package com.aforce3.trailertracer.optimizations;

import java.util.HashMap;
import java.util.HashSet;

import javax.media.j3d.Transform3D;
import javax.vecmath.Matrix3d;
import javax.vecmath.Point3d;
import javax.vecmath.SingularMatrixException;
import javax.vecmath.Vector3d;

import com.aforce3.trailertracer.Ray;
import com.aforce3.trailertracer.scenes.Scene;

public abstract class BoundingBox
{
	Point3d upperLeftBack;
	Point3d lowerRightFront;
	
	public BoundingBox(Point3d upperLeftBack, Point3d lowerRightFront)
	{
		this.upperLeftBack = new Point3d(upperLeftBack);
		this.lowerRightFront = new Point3d(lowerRightFront);
	}
	
	public Point3d getUpperLeftBack()
	{
		return upperLeftBack;
	}
	
	public Point3d getLowerRightFront()
	{
		return lowerRightFront;
	}
	
	public void setUpperLeftBack(Point3d p)
	{
		upperLeftBack.set(p);
	}
	
	public void setLowerRightFront(Point3d p)
	{
		lowerRightFront.set(p);
	}
	
	/** @deprecated - completely untested; there are likely mistakes **/
	public boolean intersects(BoundingBox boundingBox)
	{
		// The other box is completely to the left of this bounding box
		if(boundingBox.getLowerRightFront().x < getUpperLeftBack().x) return false;

		// The other box is completely to the right of this bounding box
		if(boundingBox.getUpperLeftBack().x > getLowerRightFront().x) return false;

		
		// The other box is completely on top of this bounding box
		if(boundingBox.getLowerRightFront().y > getUpperLeftBack().y) return false;

		// The other box is completely under this bounding box
		if(boundingBox.getUpperLeftBack().y < getLowerRightFront().y) return false;
		

		// The other box is completely in front of this bounding box
		if(boundingBox.getLowerRightFront().z < getUpperLeftBack().z) return false;

		// The other box is completely behind this bounding box
		if(boundingBox.getUpperLeftBack().z > getLowerRightFront().z) return false;
		
		return true;
	}
	
	/** @deprecated - completely untested; there are likely mistakes **/
	public boolean completelyContains(BoundingBox boundingBox)
	{
		// The left side of the box is not within the box
		if(boundingBox.getUpperLeftBack().x < getUpperLeftBack().x) return false;
		
		// The right side of the box is not within the box
		if(boundingBox.getLowerRightFront().x > getLowerRightFront().x) return false;
		

		// The top side of the box is not within the box
		if(boundingBox.getUpperLeftBack().y > getUpperLeftBack().y) return false;
		
		// The bottom side of the box is not within the box
		if(boundingBox.getLowerRightFront().y < getLowerRightFront().y) return false;
		

		// The back side of the box is not within the box
		if(boundingBox.getUpperLeftBack().z < getUpperLeftBack().z) return false;
		
		// The front side of the box is not within the box
		if(boundingBox.getLowerRightFront().z > getLowerRightFront().z) return false;
		
		
		return true;
	}
	
	/** @deprecated - completely untested; there are likely mistakes **/
	public boolean contains(Point3d point)
	{
		if(point.x < getUpperLeftBack().x) return false;
		if(point.x > getLowerRightFront().x) return false;
		if(point.y > getUpperLeftBack().y) return false;
		if(point.y < getLowerRightFront().y) return false;
		if(point.z < getUpperLeftBack().z) return false;
		if(point.z < getLowerRightFront().z) return false;
		
		return true;
	}
	
	public boolean intersects(Ray ray){
		//if ray source is within box
		//if (contains(ray.getSource())) return true;

		Vector3d diagonal = new Vector3d(getUpperLeftBack());
		diagonal.sub(getLowerRightFront());
		diagonal.scale(0.5);
		double radius = diagonal.length();
		
		Point3d center = new Point3d(diagonal);
		center.add(getLowerRightFront());
		
		boolean b = intersectsSphere(ray, center, (float) radius);
		//System.out.println(b + "RAY: " + ray.getSource() + "DIRECTION: " + ray.getDirection() + "center: " + center + "radius" + radius);
		return b;
	}
	
	public boolean intersectsSphere(Ray ray, Point3d center, float radius){
		// center minus (relative to) source
		Vector3d CminusS = new Vector3d(center);
		CminusS.sub(ray.getSource());

		float LtimesC = (float) (ray.getDirection().x * CminusS.x + ray.getDirection().y * CminusS.y + ray.getDirection().z * CminusS.z);
		float CSquared = (float) (CminusS.x * CminusS.x + CminusS.y * CminusS.y + CminusS.z * CminusS.z);
		float rSquared = radius * radius;

		float inner = LtimesC * LtimesC - CSquared + rSquared;

		if(inner < 0)
		{
			return false;
		}
		else
		{
			float positive = (float) (LtimesC + Math.sqrt(inner));
			float negative = (float) (LtimesC - Math.sqrt(inner));
			// make sure it is not too close
			if(negative > 0)
			{
				return true;
			}
			else if(positive > 0)
			{
				// returning point from within sphere :D
				return true;
			}
			else
			{
				return false;
			}
		}
	}
	
	public void addBox(BoundingBox b){
		this.setUpperLeftBack(new Point3d(Math.min(b.getUpperLeftBack().x, this.getUpperLeftBack().x),
				Math.max(b.getUpperLeftBack().y, this.getUpperLeftBack().y),
				Math.min(b.getUpperLeftBack().z, this.getUpperLeftBack().z)));
		this.setLowerRightFront(new Point3d(Math.max(b.getUpperLeftBack().x, this.getUpperLeftBack().x),
				Math.min(b.getUpperLeftBack().y, this.getUpperLeftBack().y),
				Math.max(b.getUpperLeftBack().z, this.getUpperLeftBack().z)));
	}
	
	//boolean lights is a flag to get lights
	public Intersection getIntersection(Ray ray, Scene child){
		HashSet<Scene> s = new HashSet<Scene>();
		s.add(child);
		return getIntersection(ray, s, true);
	}
	
	public Intersection getIntersection(Ray ray, Scene child, boolean lights){
		HashSet<Scene> s = new HashSet<Scene>();
		s.add(child);
		return getIntersection(ray, s, lights);
	}
	
	public abstract Intersection getIntersection(Ray ray, HashSet<Scene> child, boolean lights);
	
	//does not work...
	@Deprecated
	public void applyTransform(Transform3D t){
		t.transform(upperLeftBack);
		t.transform(lowerRightFront);
	}
}

